﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Ricciolo.StylesExplorer.Model;
using ICSharpCode.TextEditor.Document;
using System.Xml.XPath;
using System.Xml;
using System.IO;
using Ricciolo.StylesExplorer.Components;
using ICSharpCode.TextEditor;
using Ricciolo.StylesExplorer.Controls;
using Ricciolo.StylesExplorer.Properties;
using System.Reflection;
using System.Security.Permissions;
using Ricciolo.StylesExplorer.Model.BamlResource;

namespace Ricciolo.StylesExplorer.Forms
{
    public partial class MainForm : Form
    {

        private ApplicationCollection _applicationCollection;
        private bool namespaceWrited;

        public MainForm()
        {
            InitializeComponent();

            this.Text = String.Format("{0} - {1}", Application.ProductName, Application.ProductVersion);

            this.PrepareEditor();
            this.PrepareTreeView();
        }

        #region Properties

        ApplicationCollection ApplicationCollection
        {
            get
            {
                if (_applicationCollection == null)
                    _applicationCollection = new ApplicationCollection(this.Application_AssemblyResolve);
                return _applicationCollection;
            }
        }

        #endregion

        #region Protected methods

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            this.BeginInvoke(new Action(StartUp));
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            CustomThreadPool.Default.Dispose();

            base.OnClosing(e);
        }

        #endregion

        private void PrepareTreeView()
        {
            this.explorerTreeView.Font = Settings.Default.ExplorerFont;
            this.explorerTreeView.Maps.Add(new TreeNodeMap() { DataType = typeof(IApplication), TreeNodeType = typeof(ApplicationTreeNode) });
            this.explorerTreeView.Maps.Add(new TreeNodeMap() { DataType = typeof(IAssembly), TreeNodeType = typeof(AssemblyTreeNode) });
            this.explorerTreeView.Maps.Add(new TreeNodeMap() { DataType = typeof(IXamlFile), TreeNodeType = typeof(XamlFileTreeNode) });
            this.explorerTreeView.Maps.Add(new TreeNodeMap() { DataType = typeof(IXamlData), TreeNodeType = typeof(XamlDataTreeNode) });
            this.explorerTreeView.DataSource = this.ApplicationCollection;
        }

        private void PrepareEditor()
        {
            // Disable text input
            textEditorControl.ActiveTextAreaControl.TextArea.KeyEventHandler += new ICSharpCode.TextEditor.KeyEventHandler(TextArea_KeyEventHandler);
            textEditorControl.Document.HighlightingStrategy = HighlightingManager.Manager.FindHighlighter("XML");
            textEditorControl.Document.FoldingManager.FoldingStrategy = new XmlFoldingStrategy();
            textEditorControl.ShowEOLMarkers = false;
            textEditorControl.ShowMatchingBracket = false;
            textEditorControl.ShowVRuler = false;
            textEditorControl.ShowSpaces = false;
            textEditorControl.ShowTabs = false;
        }

        private bool TextArea_KeyEventHandler(char ch)
        {
            return true;
        }

        private void StartUp()
        {
            string fxDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Reference Assemblies\Microsoft\Framework\v3.0\");
            if (Directory.Exists(fxDir))
            {
                this.ApplicationCollection.Add(new SingleAssemblyApplication(Path.Combine(fxDir, "PresentationFramework.Classic.dll")));
                this.ApplicationCollection.Add(new SingleAssemblyApplication(Path.Combine(fxDir, "PresentationFramework.Aero.dll")));
                this.ApplicationCollection.Add(new SingleAssemblyApplication(Path.Combine(fxDir, "PresentationFramework.Luna.dll")));
                this.ApplicationCollection.Add(new SingleAssemblyApplication(Path.Combine(fxDir, "PresentationFramework.Royale.dll")));
            }

            this.ApplicationCollection.Add(new BamlResourceApplication(@"E:\Sources\Avalon\StylesExplorer\test\resource.baml"));
        }

        private void ShowMarkupAsync(IFullXPathNavigable xmlData, IXamlFile file)
        {
            if (xmlData == null)
            {
                ShowMarkup(null, null);
                return;
            }

            UpdateXamlText("Loading...");

            CustomThreadPool.Default.QueueUserWorkItem(delegate
            {
                // Load data async
                XPathNavigator navigator = xmlData.CreateNavigator(true);

                this.BeginInvoke(new MethodInvoker(delegate
                    {
                        ShowMarkup(navigator, (file != null) ? file.Namespaces : new Dictionary<String, String>());
                    }
                ));
            }, null);
        }

        private void ShowMarkup(XPathNavigator navigator, IDictionary<String, String> namespaces)
        {
            if (navigator != null)
            {
                string xaml;
                // Show l'xml
                using (XmlReader reader = navigator.ReadSubtree())
                {
                    xaml = DumpXmlReader(namespaces, reader);
                }
                UpdateXamlText(xaml);

                // Preview xaml
                xamlPreview.SetXaml(xaml);
            }
            else
                UpdateXamlText(String.Empty);
        }

        private void UpdateXamlText(string xaml)
        {
            this.textEditorControl.EnableFolding = false;
            this.textEditorControl.Text = xaml;

            // Update folding
            this.textEditorControl.EnableFolding = true;
            this.textEditorControl.Document.FoldingManager.UpdateFoldings(string.Empty, null);
            TextArea textArea = this.textEditorControl.ActiveTextAreaControl.TextArea;
            textArea.Refresh(textArea.FoldMargin);
        }

        private string DumpXmlReader(IDictionary<String, String> namespaces, XmlReader re)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.OmitXmlDeclaration = true;

            // TODO: temp, to check
            namespaceWrited = false;

            using (StringWriter sw = new StringWriter())
            {
                sw.NewLine = "\r";

                using (XmlWriter writer = XmlTextWriter.Create(sw, settings))
                {
                    //writer.WriteNode(re, true);
                    while (re.Read())
                    {
                        WriteNode(namespaces, re, writer);
                    }
                }

                return sw.ToString();
            }
        }

        private void WriteNode(IDictionary<String, String> namespaces, XmlReader re, XmlWriter writer)
        {
            if (re.NodeType == XmlNodeType.Element)
            {
                writer.WriteStartElement(re.Prefix, re.LocalName, re.NamespaceURI);

                if (!namespaceWrited)
                {
                    WriteNamespaces(namespaces, writer);
                    namespaceWrited = true;
                }

                if (re.MoveToFirstAttribute())
                {
                    if (re.LocalName != "xmlns" && re.Prefix != "xmlns")
                    {
                        writer.WriteStartAttribute(re.Prefix, re.LocalName, re.NamespaceURI);
                        if (re.ReadAttributeValue())
                            writer.WriteString(re.Value);
                        writer.WriteEndAttribute();
                    }
                    while (re.MoveToNextAttribute())
                    {
                        if (re.LocalName != "xmlns" && re.Prefix != "xmlns")
                        {
                            writer.WriteStartAttribute(re.Prefix, re.LocalName, re.NamespaceURI);
                            if (re.ReadAttributeValue())
                                writer.WriteString(re.Value);
                            writer.WriteEndAttribute();
                        }
                    }
                    re.MoveToElement();
                }
            }
            else if (re.NodeType == XmlNodeType.EndElement)
                writer.WriteEndElement();
            else if (re.NodeType == XmlNodeType.Text)
                writer.WriteString(re.Value);
        }

        private static void WriteNamespaces(IDictionary<String, String> namespaces, XmlWriter writer)
        {
            //IDictionary<string, string> namespaces = ((IXmlNamespaceResolver)re).GetNamespacesInScope(XmlNamespaceScope.All);
            //IDictionary<string, string> namespaces = new Dictionary<String, String>();
            //namespaces.Add(String.Empty, XmlPIMapping.PresentationNamespace);
            //namespaces.Add("x", XmlPIMapping.XamlNamespace);

            foreach (KeyValuePair<string, string> pair in namespaces)
            {
                writer.WriteAttributeString("xmlns", pair.Key, "", pair.Value);
            }
        }

        #region Handlers

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Find root node
            TreeNode node = explorerTreeView.SelectedNode;
            while (node != null && node.Parent != null)
                node = node.Parent;

            if (node != null)
            {
                node.Remove();
                // Remove app
                IApplication app = ((ApplicationTreeNode)node).Application;
                this.ApplicationCollection.Remove(app);
                // Dispose the app
                if (app is IDisposable)
                    ((IDisposable)app).Dispose();
            }
        }

        private void loadToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }

        private void backToolStripButton_Click(object sender, EventArgs e)
        {
            explorerTreeView.GoBack();
        }

        private void forwardToolStripButton_Click(object sender, EventArgs e)
        {
            explorerTreeView.GoForward();
        }

        private void explorerTreeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            backToolStripButton.Enabled = (explorerTreeView.CanBack);
            forwardToolStripButton.Enabled = (explorerTreeView.CanForward);
            closeToolStripMenuItem.Enabled = (explorerTreeView.SelectedNode != null);

            IFullXPathNavigable xnav = explorerTreeView.SelectedNode as IFullXPathNavigable;

            // Find file namespaces
            IXamlFile file = null;
            TreeNode node = explorerTreeView.SelectedNode;
            while (!(node is XamlFileTreeNode) && node.Parent != null)
                node = node.Parent;
            if (node is XamlFileTreeNode)
                file = ((XamlFileTreeNode)node).XamlFile;

            // Show markup
            ShowMarkupAsync(xnav, file);
        }

        private void exedllApplicationToolStripMenuItem_Click(object sender, EventArgs e)
        {
            openFileDialog.Filter = "Assembly files (*.exe,*.dll)|*.exe;*.dll";
            if (openFileDialog.ShowDialog(this) == DialogResult.OK)
            {
                SingleAssemblyApplication saa = new SingleAssemblyApplication(openFileDialog.FileName);
                this.ApplicationCollection.Add(saa);
            }
        }

        void Application_AssemblyResolve(object sender, Ricciolo.StylesExplorer.MarkupReflection.AssemblyResolveEventArgs e)
        {
            this.Invoke(new Action(delegate()
            {
                using (FindAssemblyDialog d = new FindAssemblyDialog(new AssemblyName(e.Name), e.BaseDir))
                {
                    if (d.ShowDialog(this) == DialogResult.OK)
                        e.Location = d.AssemblyLocation;
                }
            }));
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (AboutBoxDialog ab = new AboutBoxDialog())
            {
                ab.ShowDialog(this);
            }
        }

        #endregion

    }
}
